﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace DSA
{
    public class DigitalSignatureStandard
    {
       
        public static Key GenerateKey()
        {
            var key = new Key();

            // p i q
            key.P = new BigInteger("8df2a494492276aa3d25759bb06869cbeac0d83afb8d0cf7cbb8324f0d7882e5d0762fc5b7210eafc2e9adac32ab7aac49693dfbf83724c2ec0736ee31c80291", 16);
            key.Q = new BigInteger("c773218c737ec8ee993b4f2ded30f48edace915f", 16);

            int bits = key.Q.bitCount() - 1;

            //  0 < x < q
            key.X = new BigInteger();
            key.X.genRandomBits(bits, new Random());

            // 1 < h < p - 1
            BigInteger h = new BigInteger();
            int hbits = key.P.bitCount() - 2;
            Random rand = new Random();
            BigInteger exp = (key.P - 1) / key.Q;

            // (g = h^((p - 1) / q) mod p) > 1
            while (true)
            {
                h.genRandomBits(hbits, rand);
                key.G = h.modPow(exp, key.P);
                if (key.G > 1)
                    break;
            }

            // y = g^x mod p
            key.Y = key.G.modPow(key.X, key.P);
            return key;
        }

        public static BigInteger[] Sign(Stream reader, Stream writer, Key key)
        {
            BigInteger[] rs = new BigInteger[2];

            byte[] msgbytes = new byte[reader.Length];
            reader.Read(msgbytes, 0, msgbytes.Length);

            BigInteger k = new BigInteger();
            k.genRandomBits(key.Q.bitCount() - 1, new Random());
            // r = k^p mod q
            BigInteger r = key.G.modPow(k, key.P) % key.Q;
            BigInteger invk = k.modInverse(key.Q);
            // s = (k^(-1) * (SHA1(M) + x * r)) mod q
            BigInteger s = (invk * ((new BigInteger(new SHA1CryptoServiceProvider().ComputeHash(msgbytes)) + (key.X * r)) % key.Q)) % key.Q;

            rs[0] = r;
            rs[1] = s;

            byte[] rbytes = r.getBytes();
            byte[] sbytes = s.getBytes();
            
            // Zapisujemy R
            writer.WriteByte((byte)rbytes.Length);
            writer.Write(rbytes, 0, rbytes.Length);
            // Zapisujemy S
            writer.WriteByte((byte)sbytes.Length);
            writer.Write(sbytes, 0, sbytes.Length);
            // Zapisujemy wiadomość
            writer.Write(msgbytes, 0, msgbytes.Length);

            return rs;
        }

        public static BigInteger[] Verify(Stream reader, Key key)
        {
            BigInteger[] rv = new BigInteger[3];

            // Odczytujemy R
            byte[] rbytes = new byte[reader.ReadByte()];
            reader.Read(rbytes, 0, rbytes.Length);
            BigInteger r = new BigInteger(rbytes);

            // Odczytujemy S
            byte[] sbytes = new byte[reader.ReadByte()];
            reader.Read(sbytes, 0, sbytes.Length);
            BigInteger s = new BigInteger(sbytes);

            // Odczytujemy wiadomość
            byte[] msgbytes = new byte[reader.Length - reader.Position];
            reader.Read(msgbytes, 0, msgbytes.Length);

            // Sprawdz czy r => 0 < r < q
            if (r <= 0 || r >= key.Q)
            {
                rv[0] = rv[1] = rv[2] = 0;
                return rv;
            }
            // Sprawdz czy s => 0 < s < q
            if (s <= 0 || s >= key.Q)
            {
                rv[0] = rv[1] = rv[2] = 0;
                return rv;
            }
            // w = (s')^(-1) mod q
            BigInteger w = s.modInverse(key.Q);
            // u1 = ((SHA1(M') * w) mod q
            BigInteger u1 = (new BigInteger(new SHA1CryptoServiceProvider().ComputeHash(msgbytes)) * w) % key.Q;
            // u2 = ((r') * w) mod q
            BigInteger u2 = (r * w) % key.Q;
            // v = ((g^u1 * y^u2) mod p) mod q
            BigInteger v = ((key.G.modPow(u1, key.P) * key.Y.modPow(u2, key.P)) % key.P) % key.Q;
	
			Console.WriteLine("r = " + r);
            Console.WriteLine("v = " + v);
            // v == r'
            if (v == r) rv[0] = 1;
            else rv[0] = 0;

            rv[1] = r;
            rv[2] = v;

            return rv;
        }
    }

    public class Key
    {
        public BigInteger P { get; set; }
        public BigInteger Q { get; set; }
        public BigInteger G { get; set; }
        public BigInteger Y { get; set; }
        public BigInteger X { get; set; }
    }
}
